package com.longy.desensitized.util.interceptor;


import com.longy.desensitized.util.EncryptDecryptUtil;
import com.longy.desensitized.util.annotation.SensitiveDataClass;

import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class)
})
public class ParameterEncryptInterceptor implements Interceptor {

    // 更新时的参数名称，ParamMap的key值
    private static final String CRYPT = "ew";
    private static final String CRYPT2 = "et";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            // @Signature 指定了 type= parameterHandler.class 后，这里的 invocation.getTarget() 便是parameterHandler
            // 若指定ResultSetHandler，这里则能强转为ResultSetHandler
            ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
            // 获取参数对像，即对应MyBatis的 mapper 中 paramsType 的实例
            Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
            parameterField.setAccessible(true);
            // 取出参数实例
            Object parameterObject = parameterHandler.getParameterObject();
            if (parameterObject != null) {
                Object sensitiveObject = null;
                if (parameterObject instanceof MapperMethod.ParamMap) {
                    // 更新操作被拦截
                    Map paramMap = (Map) parameterObject;
                    try {
                        sensitiveObject = paramMap.get(CRYPT2);
                    } catch (Exception e) {
                        sensitiveObject = paramMap.get(CRYPT);
                    }

                } else {
                    // 插入操作被拦截，parameterObject即为待插入的实体对象
                    sensitiveObject = parameterObject;
                }
                // 获取不到数据就直接放行
                if (Objects.isNull(sensitiveObject)) {
                    return invocation.proceed();
                }
                Class<?> sensitiveObjectClass = sensitiveObject.getClass();
                SensitiveDataClass sensitiveData = AnnotationUtils.findAnnotation(sensitiveObjectClass,
                                                                                  SensitiveDataClass.class);
                if (Objects.nonNull(sensitiveData)) {
                    // 如果是被注解的类，则进行加密
                    // 取出当前当前类所有字段，传入加密方法
                    Field[] declaredFields = sensitiveObjectClass.getDeclaredFields();
                    EncryptDecryptUtil.encrypt(declaredFields, sensitiveObject);
                }
            }
            return invocation.proceed();
        } catch (Exception e) {
            // 未作更多处理，加密失败仍然会放行让数据进入数据库
            log.error("加密失败", e);
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }

    /**
     * 对象需要加密或解密
     **/
    public static boolean needDecryptOrDecrypt(Object object) {
        if (ObjectUtils.isEmpty(object)) {
            return false;
        }
        Class<?> objectClass = ClassUtils.getUserClass(object);
        SensitiveDataClass sensitiveDataClass = AnnotationUtils.findAnnotation(objectClass,
                                                                               SensitiveDataClass.class);
        return Objects.nonNull(sensitiveDataClass);
    }
}
